﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using GenericRepository.Infrastructure;
using System.Data.Entity;

namespace GenericRepository
{
    public  class Repository : IRepository
    {
        private ObjectContext Context { get; set; }
        private IUnitWork UnitWork;

        #region Private Methods

        private ObjectContext ObjectContext
        {
            get
            {
                if (this.Context == null)
                {

                    this.Context = ContextFactory.GetContext();

                }
                return this.Context;
            }
        }

        private EntityKey GetEntityKey<TEntity>(object keyValue) where TEntity : class
        {
            var entitySetName = GetEntityName<TEntity>();
            var objectSet = this.ObjectContext.CreateObjectSet<TEntity>();
            var keyPropertyName = objectSet.EntitySet.ElementType.KeyMembers[0].ToString();
            var entityKey = new EntityKey(entitySetName, new[] { new EntityKeyMember(keyPropertyName, keyValue) });
            return entityKey;
        }

        private string GetEntityName<TEntity>() where TEntity : class
        {
            return string.Format("{0}.{1}", this.ObjectContext.DefaultContainerName, typeof(TEntity).Name);
        }

        protected string GetEntitySetName(string entityTypeName)
        {
            return this.Context.MetadataWorkspace
                                .GetEntityContainer(this.Context.DefaultContainerName, DataSpace.CSpace)
                                .BaseEntitySets
                                .Single(x => x.ElementType.Name.Equals(entityTypeName)).Name;
        }

        #endregion

        #region Public Methods

        public IUnitWork UnitOfWork
        {
            get
            {
                if (UnitWork == null)
                {
                    UnitWork = new UnitWork(this.ObjectContext);
                }
                return UnitWork;
            }
        }

        public Repository(ObjectContext objectContext)
        {
            if (objectContext == null)
                throw new ArgumentNullException("objectContext");
            this.Context = objectContext;
        }

        public IQueryable<T> GetAll<T>() where T : class
        {
            return this.ObjectContext.CreateObjectSet<T>();
        }

        public IQueryable<T> Find<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            return this.ObjectContext.CreateQuery<T>(GetEntityName<T>()).Where(predicate);
        }

        public T GetOne<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            return this.ObjectContext.CreateQuery<T>(GetEntityName<T>()).Single(predicate);
        }

        public T GetByKey<T>(object id) where T : class
        {
            EntityKey key = GetEntityKey<T>(id);

            object originalItem;
            if (this.ObjectContext.TryGetObjectByKey(key, out originalItem))
            {
                return (T)originalItem;
            }
            return default(T);
        }

        public void Add<T>(T entity) where T : class
        {
            this.ObjectContext.AddObject(GetEntityName<T>(), entity);
        }

        public void Delete<T>(T entity) where T : class
        {
            this.ObjectContext.DeleteObject(entity);
        }

        public void Delete<T>(IQueryable<T> entities) where T : class
        {
            foreach (var entity in entities)
            {
                Delete(entity);
            }
        }

        public void Update<T>(T entity) where T : class
        {
            var fqen = GetEntityName<T>();

            object originalItem;
            EntityKey key = this.ObjectContext.CreateEntityKey(fqen, entity);
            if (this.ObjectContext.TryGetObjectByKey(key, out originalItem))
            {
               this.ObjectContext.ApplyCurrentValues(key.EntitySetName, entity);
            }
        }

        public int Count<T>() where T : class
        {
            return GetQuery<T>().Count();
        }

        public IQueryable<T> GetQuery<T>() where T : class
        {
            var entityName = GetEntityName<T>();
            return this.ObjectContext.CreateQuery<T>(entityName);
        }

        public IQueryable<T> GetQuery<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            return GetQuery<T>().Where(predicate);
        }

        #endregion


    }
}